﻿using Cofe.Core.Utils;
using Cofe.Core.Validater;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Cofe.Core
{
    public static class PropertyPairUtils
    {
        public enum OpType { Add, Or }

        public static Expression EvaluateExpression(OpType opType, Expression expression1, Expression expression2)
        {
            switch (opType)
            {
                case OpType.Add:
                    return Expression.Add(expression1, expression2);
                case OpType.Or:
                    return Expression.Or(expression1, expression2);
                default:
                    throw new NotSupportedException();
            }
        }


        public static T getResult<T>(Expression expr)
        {
            return Expression.Lambda<Func<T>>(expr).Compile()();
        }
        public static object Evaluate(Type type, OpType opType, object value1, object value2)
        {
            Expression expr = EvaluateExpression(opType, Expression.Constant(value1), Expression.Constant(value2));
            MethodInfo methodLambda = typeof(PropertyPairUtils).GetMethod("getResult")
               .MakeGenericMethod(type);
            return methodLambda.Invoke(null, new object[] { expr });
        }

        public static T Evaluate<T>(OpType opType, T value1, T value2)
        {

            return (T)Evaluate(typeof(T), opType, value1, value2);
        }


        public static object Combine(OpType opType, PropertyTypeAttribute pTypeAttribute, InvokeBehaviorAttribute[] invokeBehaviors, object value1, object value2)
        {
            if (pTypeAttribute is ActionAttribute)
            {
                ActionAttribute actionAttribute = pTypeAttribute as ActionAttribute;
                switch (actionAttribute.ActionType)
                {
                    case ActionType.Sync:
                        if (actionAttribute.ItemType != null)
                            return _calculateActionGeneric.MakeGenericMethod(actionAttribute.ItemType).Invoke(
                           null, new object[] { opType, actionAttribute, invokeBehaviors, value1, value2 });
                        else if (actionAttribute.OutputType != null)
                            return _calculateActionGeneric.MakeGenericMethod(actionAttribute.OutputType).Invoke(
                            null, new object[] { opType, actionAttribute, invokeBehaviors, value1, value2 });
                        else
                            return CalculateAction(opType, actionAttribute, invokeBehaviors, value1, value2);

                    case ActionType.Async:
                        if (actionAttribute.ItemType != null)
                            return _calculateTaskGeneric.MakeGenericMethod(actionAttribute.ItemType).Invoke(
                            null, new object[] { opType, actionAttribute, invokeBehaviors, value1, value2 });
                        else if (actionAttribute.OutputType != null)
                            return _calculateTaskGeneric.MakeGenericMethod(actionAttribute.OutputType).Invoke(
                            null, new object[] { opType, actionAttribute, invokeBehaviors, value1, value2 });
                        else
                            return CalculateTask(opType, actionAttribute, invokeBehaviors, value1, value2);

                    default:
                        throw new NotSupportedException();
                }
            }
            else
                return CalculateSimpleValue(opType, pTypeAttribute.Types[0], value1, value2);
        }

        public static object CalculateSimpleValue(OpType opType, Type type, object value1, object value2)
        {
            return Evaluate(type, opType, value1, value2);
        }

#if NETFX_CORE
        private static MethodInfo _calculateActionGeneric = typeof(PropertyPairUtils).GetMethods("CalculateAction")
        .Single((m) => m.Name == "CalculateAction" && m.IsGenericMethod);
        private static MethodInfo _calculateTaskGeneric = typeof(PropertyPairUtils).GetMethods("CalculateTask")
        .Single((m) => m.Name == "CalculateTask" && m.IsGenericMethod);
#else
        private static MethodInfo _calculateActionGeneric = typeof(PropertyPairUtils).GetMethods(BindingFlags.Static | BindingFlags.Public)
        .Single((m) => m.Name == "CalculateAction" && m.IsGenericMethod);
        private static MethodInfo _calculateTaskGeneric = typeof(PropertyPairUtils).GetMethods(BindingFlags.Static | BindingFlags.Public)
        .Single((m) => m.Name == "CalculateTask" && m.IsGenericMethod);
#endif

        private static void evaluateInvokeBehaviors(ActionAttribute act, InvokeBehaviorAttribute[] invokeBehaviors,
         out bool isAggregate, out bool isHandleFirst, out bool isList)
        {
            isAggregate = invokeBehaviors.Any(ib => ib is AggregateResultAttribute);
            isHandleFirst = invokeBehaviors.Any(ib => ib is HandleOnesAttribute);
            isList = act is ListActionAttribute;
        }


        public static Action<ParameterDic> CalculateAction(OpType opType, ActionAttribute act, InvokeBehaviorAttribute[] invokeBehaviors, object value1, object value2)
        {
            bool isAggregate, isHandleFirst, isList;
            evaluateInvokeBehaviors(act, invokeBehaviors, out isAggregate, out isHandleFirst, out isList);

            Action<ParameterDic> func1 = (Action<ParameterDic>)value1;
            Action<ParameterDic> func2 = (Action<ParameterDic>)value2;

            if (!isAggregate && !isHandleFirst && !isList)
                return func1;

            if (isAggregate)
                return actionAggregate(opType, func1, func2);

            if (isHandleFirst)
                return actionFirstHandled(func1, func2);

            throw new NotSupportedException();
        }

        public static object CalculateAction<T>(OpType opType, ActionAttribute act, InvokeBehaviorAttribute[] invokeBehaviors, object value1, object value2)
        {
            bool isAggregate, isHandleFirst, isList;
            evaluateInvokeBehaviors(act, invokeBehaviors, out isAggregate, out isHandleFirst, out isList);

            if (isList)
                return actionList<T>(opType, (Func<ParameterDic, IEnumerable<T>>)value1, (Func<ParameterDic, IEnumerable<T>>)value2, isAggregate, isHandleFirst);

            Func<ParameterDic, T> func1 = (Func<ParameterDic, T>)value1;
            Func<ParameterDic, T> func2 = (Func<ParameterDic, T>)value2;

            if (!isAggregate && !isHandleFirst && !isList)
                return func1;

            if (isAggregate)
                return actionAggregate<T>(opType, func1, func2);

            if (isHandleFirst)
                return actionFirstHandled<T>(func1, func2);

            throw new NotSupportedException();
        }



        public static Func<ParameterDic, Task> CalculateTask(OpType opType, ActionAttribute act, InvokeBehaviorAttribute[] invokeBehaviors, object value1, object value2)
        {
            bool isAggregate, isHandleFirst, isList;
            evaluateInvokeBehaviors(act, invokeBehaviors, out isAggregate, out isHandleFirst, out isList);

            Func<ParameterDic, Task> func1 = (Func<ParameterDic, Task>)value1;
            Func<ParameterDic, Task> func2 = (Func<ParameterDic, Task>)value2;

            if (!isAggregate && !isHandleFirst)
                return func1;

            if (isAggregate)
                return taskAggregate(opType, func1, func2);

            if (isHandleFirst)
                return taskFirstHandled(func1, func2);

            throw new NotSupportedException();
        }


        public static object CalculateTask<T>(OpType opType, ActionAttribute act, InvokeBehaviorAttribute[] invokeBehaviors, object value1, object value2)
        {
            bool isAggregate, isHandleFirst, isList;
            evaluateInvokeBehaviors(act, invokeBehaviors, out isAggregate, out isHandleFirst, out isList);

            if (isList)
            {
                if (isHandleFirst)
                    return taskListFirstHandled<T>(opType, (Func<ParameterDic, Task<IEnumerable<T>>>)value1,
                        (Func<ParameterDic, Task<IEnumerable<T>>>)value2);
                return taskListAggregate<T>(opType, (Func<ParameterDic, Task<IEnumerable<T>>>)value1,
                    (Func<ParameterDic, Task<IEnumerable<T>>>)value2);
            }

            Func<ParameterDic, Task<T>> func1 = (Func<ParameterDic, Task<T>>)value1;
            Func<ParameterDic, Task<T>> func2 = (Func<ParameterDic, Task<T>>)value2;

            if (!isAggregate && !isHandleFirst)
                return func1;

            if (isAggregate)
                return taskAggregate<T>(opType, func1, func2);

            if (isHandleFirst)
                return taskFirstHandled<T>(func1, func2);

            throw new NotSupportedException();
        }

        private static Action<ParameterDic> actionAggregate(OpType opType, Action<ParameterDic> value1, Action<ParameterDic> value2)
        {
            return (pmd) => { value1(pmd); value2(pmd); };
        }

        private static Action<ParameterDic> actionFirstHandled(Action<ParameterDic> value1, Action<ParameterDic> value2)
        {
            return (pmd) => { value1(pmd); if (!(pmd.IsHandled)) value2(pmd); };
        }

        private static Func<ParameterDic, T> actionAggregate<T>(OpType opType, Func<ParameterDic, T> value1, Func<ParameterDic, T> value2)
        {
            return (pmd) => Evaluate<T>(opType, value1(pmd), value2(pmd));
        }

        private static Func<ParameterDic, T> actionFirstHandled<T>(Func<ParameterDic, T> value1, Func<ParameterDic, T> value2)
        {
            return (pmd) =>
            {
                var result = value1(pmd);
                return pmd.IsHandled ? result : value2(pmd);
            };
        }

        private static Func<ParameterDic, IEnumerable<T>> actionList<T>(OpType opType, Func<ParameterDic, IEnumerable<T>> value1,
            Func<ParameterDic, IEnumerable<T>> value2, bool isAggregate, bool isHandleFirst)
        {
            value1 = value1 ?? ((pm) => new List<T>());
            value2 = value2 ?? ((pm) => new List<T>());

            if (isAggregate)
                return (pmd) => value1(pmd).Union(value2(pmd)).Distinct();
            else if (isHandleFirst)
            {
                return (pmd) =>
                    {
                        var retVal = value1(pmd);
                        if (pmd.IsHandled) return retVal;
                        else return value2(pmd);
                    };
            }
            else return (pmd) => value1(pmd);
        }

        private static Func<ParameterDic, Task> taskAggregate(OpType opType, Func<ParameterDic, Task> value1, Func<ParameterDic, Task> value2)
        {
            return (pmd) => Task.Run(async () =>
            {
                await value1(pmd); await value2(pmd);
            });
        }

        private static Func<ParameterDic, Task> taskFirstHandled(Func<ParameterDic, Task> value1, Func<ParameterDic, Task> value2)
        {
            return (pmd) => Task.Run(async () => { await value1(pmd); if (!(pmd.IsHandled)) await value2(pmd); });
        }

        private static Func<ParameterDic, Task<T>> taskAggregate<T>(OpType opType, Func<ParameterDic, Task<T>> value1, Func<ParameterDic, Task<T>> value2)
        {
            return (pmd) => Task<T>.Run(async () => { return Evaluate<T>(opType, await value1(pmd), await value2(pmd)); });
        }

        private static Func<ParameterDic, Task<T>> taskFirstHandled<T>(Func<ParameterDic, Task<T>> value1, Func<ParameterDic, Task<T>> value2)
        {
            return (pmd) => Task<T>.Run(async () =>
            {
                var retVal = await value1(pmd);
                return (pmd.IsHandled) ? retVal : await value2(pmd);
            });
        }

        private static Func<ParameterDic, Task<IEnumerable<T>>> taskListAggregate<T>(OpType opType, Func<ParameterDic, Task<IEnumerable<T>>> value1, Func<ParameterDic, Task<IEnumerable<T>>> value2)
        {
            return (pmd) => Task<T>.Run(async () =>
            {
                var output1 = value1 != null ? await value1(pmd) : null;
                var output2 = value2 != null ? await value2(pmd) : null;
                if (output1 == null) return output2;
                if (output2 == null) return output1;

                return output1.Union(output2).Distinct();
            });
        }

        private static Func<ParameterDic, Task<IEnumerable<T>>> taskListFirstHandled<T>(OpType opType, Func<ParameterDic, Task<IEnumerable<T>>> value1, Func<ParameterDic, Task<IEnumerable<T>>> value2)
        {
            return (pmd) => Task<T>.Run(async () =>
            {
                var output1 = value1 != null ? await value1(pmd) : null;
                if (pmd.IsHandled)
                    return output1;

                var output2 = value2 != null ? await value2(pmd) : null;
                if (pmd.IsHandled)
                    return output2;

                return new List<T>();
            });
        }
    }
}
